/* 
 * Copyright (c) 2009 Levente Farkas
 * Copyright (C) 2007 Wayne Meissner
 * Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
 *                    2004 Wim Taymans <wim@fluendo.com>
 * 
 * This file is part of gstreamer-java.
 *
 * This code is free software: you can redistribute it and/or modify it under 
 * the terms of the GNU Lesser General Public License version 3 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License 
 * version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with this work.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.gstreamer;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import org.gstreamer.lowlevel.GstAPI;
import org.gstreamer.lowlevel.GstNative;
import org.gstreamer.lowlevel.GstElementAPI;
import org.gstreamer.lowlevel.GstAPI.GstCallback;

import com.sun.jna.Pointer;

import static org.gstreamer.lowlevel.GObjectAPI.GOBJECT_API;

/**
 * Abstract base class for all pipeline elements.
 * <p>
 * Element is the abstract base class needed to construct an element that
 * can be used in a GStreamer pipeline. Please refer to the plugin writers
 * guide for more information on creating Element subclasses.
 * <p>
 * The name of a Element can be retrieved with {@link #getName} and set with
 * {@link #setName}.
 * <p>
 * All elements have pads (of the type {@link Pad}).  These pads link to pads on
 * other elements.  {@link Buffer}s flow between these linked pads.
 * An Element has a list of {@link Pad} structures for all their input (or sink)
 * and output (or source) pads.
 * Core and plug-in writers can add and remove pads with {@link #addPad}
 * and {@link #removePad}.
 * <p>
 * A pad of an element can be retrieved by name with {@link #getPad}.
 * An list of all pads can be retrieved with {@link #getPads}.
 * <p>
 * Elements can be linked through their pads.
 * If the link is straightforward, use the {@link #link}
 * convenience function to link two elements, or {@link #linkMany}
 * for more elements in a row.
 * <p>
 * For finer control, use {@link #linkPads} and {@link #linkPadsFiltered}
 * to specify the pads to link on each element by name.
 * <p>
 * Each element has a state (see {@link State}).  You can get and set the state
 * of an element with {@link #getState} and {@link #setState}.
 *
 */
public class Element extends GstObject {
    private static final GstElementAPI gst = GstNative.load(GstElementAPI.class);

    @SuppressWarnings("unused")    
    private static Logger logger = Logger.getLogger(Element.class.getName());
    
    /** 
     * Creates a new instance of Element.  This constructor is used internally.
     * 
     * @param init internal initialization data.
     */
    public Element(Initializer init) { 
        super(init);
    }
    
    /**
     * Creates an instance of the required element type, but does not wrap it in 
     * a proxy.
     * 
     * @param factoryName The name of the factory to use to produce the Element
     * @param elementName The name to assign to the created Element
     * @return a raw element.
     */
    protected static Initializer makeRawElement(String factoryName, String elementName) {
        return initializer(ElementFactory.makeRawElement(factoryName, elementName));
    }
    
    /**
     * Links this element to another element. 
     * The link must be from source to destination; the other direction will not 
     * be tried. 
     * <p>
     * The function looks for existing pads that aren't linked yet. 
     * It will request new pads if necessary. Such pads need to be released manualy when unlinking.
     * If multiple links are possible, only one is established.
     *<p>
     * Make sure you have added your elements to a bin or pipeline with
     * {@link Bin#add} or {@link Bin#addMany} before trying to link them.
     *
     * @param dest The {@link Element} containing the destination pad.
     * @return true if the elements could be linked, false otherwise.
     */
    public boolean link(Element dest) {
        return gst.gst_element_link(this, dest);
    }
    
    /**
     * Chain together a series of elements, with this element as the first in the list. 
     * <p>
     * Make sure you have added your elements to a bin or pipeline with
     * {@link Bin#add} or {@link Bin#addMany} before trying to link them.
     *
     * @param elems The list of elements to be linked.
     * @return true if the elements could be linked, false otherwise.
     */
    public boolean link(Element... elems) {
        // Its much more efficient to copy the array and let the native code do the linking
        Element[] list = new Element[elems.length + 1];
        list[0] = this;
        System.arraycopy(elems, 0, list, 1, elems.length);
        return linkMany(list);
    }
    /**
     * Unlinks all source pads of this source element with all sink pads
     * of the sink element to which they are linked.
     *<p>
     * If the link has been made using {@link #link}, it could have created an
     * requestpad, which has to be released using gst_element_release_request_pad().
     * 
     * @param dest The sink Element to unlink.
     */
    public void unlink(Element dest) {
        gst.gst_element_unlink(this, dest);
    }
    
    /**
     * Sets the state of the element. 
     * <p>
     * This method will try to set the requested state by going through all the 
     * intermediary states.
     * <p>
     * This function can return {@link StateChangeReturn#ASYNC}, in which case the
     * element will perform the remainder of the state change asynchronously in
     * another thread.
     * <p>
     * An application can use {@link #getState} to wait for the completion
     * of the state change or it can wait for a state change message on the bus.
     *
     * @param state the element's new {@link State}.
     * @return the status of the element's state change.
     */
    public StateChangeReturn setState(State state) {
        return gst.gst_element_set_state(this, state);
    }
    
    /**
     * Sets the {@link Caps} on this Element.
     * 
     * @param caps the new Caps to set.
     */
    public void setCaps(Caps caps) {
        GOBJECT_API.g_object_set(this, "caps", caps);
    }
    
    /**
     * @deprecated Use {@link #getStaticPad}
     */
    @Deprecated
    public Pad getPad(String padname) {
        return gst.gst_element_get_static_pad(this, padname);
    }
    
    /**
     * Retrieves a pad from the element by name. This version only retrieves
     * already-existing (i.e. 'static') pads.
     * 
     * @param padname The name of the {@link Pad} to get.
     * @return The requested {@link Pad} if found, otherwise null.
     */
    public Pad getStaticPad(String padname) {
        return gst.gst_element_get_static_pad(this, padname);
    }
    
    /**
     *  Retrieves a list of the element's pads. 
     *
     * @return the List of {@link Pad}s.
     */
    public List<Pad> getPads() {
        return new GstIterator<Pad>(gst.gst_element_iterate_pads(this), Pad.class).asList();
    }
    
    /**
     *  Retrieves a list of the element's source pads. 
     *
     * @return the List of {@link Pad}s.
     */
    public List<Pad> getSrcPads() {
        return new GstIterator<Pad>(gst.gst_element_iterate_src_pads(this), Pad.class).asList();
    }
    
    /**
     *  Retrieves a list of the element's sink pads. 
     *
     * @return the List of {@link Pad}s.
     */
    public List<Pad> getSinkPads() {
        return new GstIterator<Pad>(gst.gst_element_iterate_sink_pads(this), Pad.class).asList();
    }
    /**
     * Adds a {@link Pad} (link point) to the Element. 
     * The Pad's parent will be set to this element.
     *<p>
     * Pads are not automatically activated so elements should perform the needed
     * steps to activate the pad in case this pad is added in the PAUSED or PLAYING
     * state. See {@link Pad#setActive} for more information about activating pads.
     *<p>
     * This function will emit the {@link PAD_ADDED} signal on the element.
     *
     * @param pad The {@link Pad} to add.
     * @return true if the pad could be added.  This function can fail when
     * a pad with the same name already existed or the pad already had another
     * parent. 
     */
    public boolean addPad(Pad pad) {
        return gst.gst_element_add_pad(this, pad);
    }
    
    /**
     * Retrieves a pad from the element by name. This version only retrieves
     * request pads. The pad must be released with {@link #releaseRequestPad}.
     * 
     * @param name the name of the request {@link Pad} to retrieve.
     * @return the requested Pad if found, otherwise <tt>null</tt>. 
     * Release using {@link #releaseRequestPad} after usage.
     */
    public Pad getRequestPad(String name) {
        return gst.gst_element_get_request_pad(this, name);
    }
    
    /**
     * Frees the previously requested pad obtained via {@link #getRequestPad}.
     * 
     * @param pad the pad to release.
     */
    public void releaseRequestPad(Pad pad) {
        gst.gst_element_release_request_pad(this, pad);
    }
    
    /**
     * Remove a {@link Pad} from the element.
     * <p>
     * This method is used by plugin developers and should not be used
     * by applications. Pads that were dynamically requested from elements
     * with {@link #getRequestPad} should be released with the
     * {@link #releaseRequestPad} function instead.
     *<p>
     * Pads are not automatically deactivated so elements should perform the needed
     * steps to deactivate the pad in case this pad is removed in the PAUSED or
     * PLAYING state. See {@link Pad#setActive} for more information about
     * deactivating pads.
     *<p>
     * This function will emit the {@link PAD_REMOVED} signal on the element.
     *
     * @param pad The {@link Pad} to remove.
     * @return true if the pad could be removed. Can return false if the
     * pad does not belong to the provided element.
     */
    public boolean removePad(Pad pad) {
        return gst.gst_element_remove_pad(this, pad);
    }
    
    /**
     * Gets the state of the element.
     * <p>
     * This method will wait until any async state change has completed.
     * 
     * @return The {@link State} the Element is currently in.
     */
    public State getState() {
        return getState(-1);
    }
    
    /**
     * Gets the state of the element.
     * <p>
     * For elements that performed an ASYNC state change, as reported by
     * {@link #setState}, this function will block up to the
     * specified timeout value for the state change to complete.
     * 
     * @param timeout the amount of time to wait.
     * @param units the units of the <tt>timeout</tt>.
     * @return The {@link State} the Element is currently in.
     *
     */
    public State getState(long timeout, TimeUnit units) {
        State[] state = new State[1];
        gst.gst_element_get_state(this, state, null, units.toNanos(timeout));
        return state[0];
    }
    
    /**
     * Gets the state of the element.
     * <p>
     * For elements that performed an ASYNC state change, as reported by
     * {@link #setState}, this function will block up to the
     * specified timeout value for the state change to complete.
     * 
     * @param timeout The amount of time in nanoseconds to wait.
     * @return The {@link State} the Element is currently in.
     *
     */
    public State getState(long timeout) {
        State[] state = new State[1];
        gst.gst_element_get_state(this, state, null, timeout);
        return state[0];
    }
    
    /**
     * Gets the state of the element.
     * <p>
     * For elements that performed an ASYNC state change, as reported by
     * {@link #setState}, this function will block up to the
     * specified timeout value for the state change to complete.
     * 
     * @param timeout The amount of time in nanoseconds to wait.
     * @param states an array to store the states in.  Must be of sufficient size
     * to hold two elements.
     *
     */
    public void getState(long timeout, State[] states) {
        State[] state = new State[1];
        State[] pending = new State[1];
        gst.gst_element_get_state(this, state, pending, timeout);
        states[0] = state[0];
        states[1] = pending[0];
    }
    
    /**
     * Tries to change the state of the element to the same as its parent.
     * If this function returns false, the state of element is undefined.
     * @return true, if the element's state could be synced to the parent's state. MT safe.
     */
    public boolean syncStateWithParent() {
    	return gst.gst_element_sync_state_with_parent(this);
    }
    
    /**
     * Retrieves the factory that was used to create this element.
     * @return the {@link ElementFactory} used for creating this element.
     */
    public ElementFactory getFactory() {
        return gst.gst_element_get_factory(this);
    }
    
    /**
     * Get the bus of the element. Note that only a {@link Pipeline} will provide a
     * bus for the application.
     *
     * @return the element's {@link Bus}
     */
    public Bus getBus() {
        return gst.gst_element_get_bus(this);
    }
    
    /**
     * Sends an event to an element.
     * <p>
     * If the element doesn't implement an event handler, the event will be 
     * pushed on a random linked sink pad for upstream events or a random 
     * linked source pad for downstream events.
     *
     * @param ev The {@link Event} to send.
     * @return true if the event was handled.
     */
    public boolean sendEvent(Event ev) {
        return gst.gst_element_send_event(this, ev);
    }
    
    /**
     * Signal emitted when an {@link Pad} is added to this {@link Element}
     * 
     * @see #connect(PAD_ADDED)
     * @see #disconnect(PAD_ADDED)
     */
    public static interface PAD_ADDED {
        /**
         * Called when a new {@link Pad} is added to an Element.
         * 
         * @param element the element the pad was added to.
         * @param pad the pad which was added.
         */
        public void padAdded(Element element, Pad pad);
    }
    
    /**
     * Signal emitted when an {@link Pad} is removed from this {@link Element}
     * 
     * @see #connect(PAD_REMOVED)
     * @see #disconnect(PAD_REMOVED)
     */
    public static interface PAD_REMOVED {
        /**
         * Called when a new {@link Pad} is removed from an Element.
         * 
         * @param element the element the pad was removed from.
         * @param pad the pad which was removed.
         */
        public void padRemoved(Element element, Pad pad);
    }
    
    /**
     * Signal emitted when this {@link Element} ceases to generated dynamic pads.
     * 
     * @see #connect(NO_MORE_PADS)
     * @see #disconnect(NO_MORE_PADS)
     */
    public static interface NO_MORE_PADS {
        /**
         * Called when an {@link Element} ceases to generated dynamic pads.
         * 
         * @param element the element which posted this message.
         */
        public void noMorePads(Element element);
    }
    
    /**
     * Signal emitted when this {@link Element} has a {@link Buffer} ready.
     * 
     * @see #connect(HANDOFF)
     * @see #disconnect(HANDOFF)
     */
    public static interface HANDOFF {
        /**
         * Called when an {@link Element} has a {@link Buffer} ready.
         * 
         * @param element the element which has a buffer ready.
         * @param buffer the buffer for the data.
         * @param pad the pad on the element.
         */
        public void handoff(Element element, Buffer buffer, Pad pad);
    }
    
    /**
     * Add a listener for the <code>pad-added</code> signal
     * 
     * @param listener Listener to be called when a {@link Pad} is added to the {@link Element}.
     */
    public void connect(final PAD_ADDED listener) {
        connect(PAD_ADDED.class, listener, new GstCallback() {
            @SuppressWarnings("unused")
            public void callback(Element elem, Pad pad, Pointer user_data) {
                listener.padAdded(elem, pad);
            }
        });
    }
    
    /**
     * Remove a listener for the <code>pad-added</code> signal
     * 
     * @param listener The listener that was previously added.
     */
    public void disconnect(PAD_ADDED listener) {
        disconnect(PAD_ADDED.class, listener);
    }
    /**
     * Add a listener for the <code>pad-added</code> signal
     * 
     * @param listener Listener to be called when a {@link Pad} is removed from the {@link Element}.
     */
    public void connect(final PAD_REMOVED listener) {
        connect(PAD_REMOVED.class, listener,new GstCallback() {
            @SuppressWarnings("unused")
            public void callback(Element elem, Pad pad, Pointer user_data) {
                listener.padRemoved(elem, pad);
            }
        });
    }
    
    /**
     * Remove a listener for the <code>pad-removed</code> signal
     * 
     * @param listener The listener that was previously added.
     */
    public void disconnect(PAD_REMOVED listener) {
        disconnect(PAD_REMOVED.class, listener);
    }
    
    /**
     * Add a listener for the <code>no-more-pads</code> signal
     * 
     * @param listener Listener to be called when the {@link Element} will has 
     * finished generating dynamic pads.
     */
    public void connect(final NO_MORE_PADS listener) {
        connect(NO_MORE_PADS.class, listener, new GstCallback() {
            @SuppressWarnings("unused")
            public void callback(Element elem, Pointer user_data) {
                listener.noMorePads(elem);
            }
        });
    }
    
    /**
     * Remove a listener for the <code>no-more-pads</code> signal
     * 
     * @param listener The listener that was previously added.
     */
    public void disconnect(NO_MORE_PADS listener) {
        disconnect(NO_MORE_PADS.class, listener);
    }
    
    /**
     * Add a listener for the <code>handoff</code> signal on this Bin
     * 
     * @param listener The listener to be called when a {@link Buffer} is ready.
     */
    public void connect(final HANDOFF listener) {
        connect(HANDOFF.class, listener, new GstAPI.GstCallback() {
            @SuppressWarnings("unused")
            public void callback(Pointer src, Buffer buffer, Pad pad, Pointer user_data) {
                listener.handoff(Element.this, buffer, pad);
            }            
        });
    }
    /**
     * Remove a listener for the <code>handoff</code> signal
     * 
     * @param listener The listener that was previously added.
     */
    public void disconnect(HANDOFF listener) {
        disconnect(HANDOFF.class, listener);
    }
    
    /**
     * Link together a list of elements.
     * <p>
     * Make sure you have added your elements to a bin or pipeline with
     * {@link Bin#add} or {@link Bin#addMany} before trying to link them.
     
     * @param elements The list of elements to link together.
     * @return true if all elements successfully linked.
     */
    public static boolean linkMany(Element... elements) {
        return gst.gst_element_link_many(elements);
    }
    
    /**
     * Unlink a list of elements.
     * 
     * @param elements The list of elements to link together
     * 
     */
    public static void unlinkMany(Element... elements) {
        gst.gst_element_unlink_many(elements);
    }
    
    /**
     * Link together source and destination pads of two elements.
     * 
     * A side effect is that if one of the pads has no parent, it becomes a
     * child of the parent of the other element.  If they have different
     * parents, the link fails.
     * 
     * @param src The {@link Element} containing the source {@link Pad}.
     * @param srcPadName The name of the source {@link Pad}.  Can be null for any pad.
     * @param dest The {@link Element} containing the destination {@link Pad}.
     * @param destPadName The name of the destination {@link Pad}.  Can be null for any pad.
     * 
     * @return true if the pads were successfully linked.
     */
    public static boolean linkPads(Element src, String srcPadName, Element dest, String destPadName) {
        return gst.gst_element_link_pads(src, srcPadName, dest, destPadName);
    }
    
    /**
     * Link together source and destination pads of two elements.
     * A side effect is that if one of the pads has no parent, it becomes a child of the parent of
     * the other element. If they have different parents, the link fails. If caps
     * is not null, makes sure that the caps of the link is a subset of caps.
     * 
     * @param src The {@link Element} containing the source {@link Pad}.
     * @param srcPadName The name of the source {@link Pad}.  Can be null for any pad.
     * @param dest The {@link Element} containing the destination {@link Pad}.
     * @param destPadName The name of the destination {@link Pad}.  Can be null for any pad.
     * @param caps The {@link Caps} to use to filter the link.
     * 
     * @return true if the pads were successfully linked.
     */
    public static boolean linkPadsFiltered(Element src, String srcPadName, 
            Element dest, String destPadName, Caps caps) {
        return gst.gst_element_link_pads_filtered(src, srcPadName, dest, destPadName, caps);
    }
    
    /**
     * Unlink source and destination pads of two elements.
     * 
     * @param src The {@link Element} containing the source {@link Pad}.
     * @param srcPadName The name of the source {@link Pad}.
     * @param dest The {@link Element} containing the destination {@link Pad}.
     * @param destPadName The name of the destination {@link Pad}.
     * 
     */
    public static void unlinkPads(Element src, String srcPadName, Element dest, String destPadName) {
        gst.gst_element_unlink_pads(src, srcPadName, dest, destPadName);
    }
    
    /**
     * Posts a {@link Message} on the element's {@link Bus}. 
     *
     * @param message the Message to post.
     * @return <tt>true</tt> if the message was posted, <tt>false</tt> if the 
     * element does not have a {@link Bus}.
     */
    public boolean postMessage(Message message) {
        return gst.gst_element_post_message(this, message);
    }
    
    /**
     * Gets the currently configured clock of the element. 
     * 
     * @return the clock of the element.
     */
    public Clock getClock() {
        return gst.gst_element_get_clock(this);
    }
    
    /**
     * Returns the base time of the element. The base time is the
     * absolute time of the clock when this element was last put to
     * PLAYING. Subtracting the base time from the clock time gives
     * the stream time of the element.
     * 
     * @return the base time of the element
     */
    public ClockTime getBaseTime() {
        return gst.gst_element_get_base_time(this);
    }
}

